# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import getpass
import sys

import svn.wc
import svn.client
import svn.core

from svn.core import SVN_AUTH_SSL_CNMISMATCH, SVN_AUTH_SSL_EXPIRED
from svn.core import SVN_AUTH_SSL_NOTYETVALID, SVN_AUTH_SSL_OTHER
from svn.core import SVN_AUTH_SSL_UNKNOWNCA


def _getpass(prompt='Password: ', stream=sys.stderr):
  """Add stream argument to getpass, as in Python 2.5."""
  try:
    return getpass.getpass(prompt, stream)
  except TypeError:
    stream.write(prompt)
    stream.flush()
    return getpass.getpass('')


def GetAuthBaton(username, interactive, config, pool, username_cb=None):
  """Return new auth callbacks and new auth baton.

  Arguments:
  username              -- username to try before prompting
  interactive           -- bool whether prompting should happen
  config                -- gvn.config.Config
  pool                  -- memory pool
  username_cb           -- optional callback to be passed each username entered
                           by the user to the prompt callbacks

  Returns:
  tuple of prompt callbacks and the auth baton at index -1; caller
  must hold references to the entire tuple, as the svn binding does not
  """
  def SimplePrompt(realm, username, may_save, pool):
    if not interactive:
        return
    result = svn.core.svn_auth_cred_simple_t()

    # XXX may_save is 1, yet svn isn't storing the password.  Hmph.
    result.may_save = may_save

    sys.stderr.write('Authentication realm: %s\n' % (realm,))
    if username is None:
      sys.stderr.write('Username: ')
      sys.stderr.flush()
      result.username = sys.stdin.readline().strip()
    else:
      result.username = username
    result.password = _getpass("Password for '%s': " % (result.username,),
                               stream=sys.stderr)
    if callable(username_cb):
      username_cb(result.username)
    return result

  def UsernamePrompt(realm, may_save, pool):
    if not interactive:
        return
    result = svn.core.svn_auth_cred_username_t()
    result.may_save = may_save
    sys.stderr.write('Authentication realm: %s\n' % (realm,))
    sys.stderr.write('Username: ')
    sys.stderr.flush()
    result.username = sys.stdin.readline().strip()
    if callable(username_cb):
      username_cb(result.username)
    return result

  def SSLServerTrustPrompt(realm, failures, cert_info, may_save, pool):
    if not interactive:
      return

    buf = ["Error validating server certificate for '%s':" % (realm,)]

    if failures & SVN_AUTH_SSL_UNKNOWNCA:
      buf.append(
          ' - The certificate is not issued by a trusted authority. Use the')
      buf.append('   fingerprint to validate the certificate manually!')

    if failures & SVN_AUTH_SSL_CNMISMATCH:
      buf.append(' - The certificate hostname does not match.')

    if failures & SVN_AUTH_SSL_NOTYETVALID:
      buf.append(' - The certificate is not yet valid.')

    if failures & SVN_AUTH_SSL_EXPIRED:
      buf.append(' - The certificate has expired.')

    if failures & SVN_AUTH_SSL_OTHER:
      buf.append(' - The certificate has an unknown error.')

    buf.extend(['Certificate information:',
                ' - Hostname: ' + cert_info.hostname,
                ' - Valid: from %s until %s' % (cert_info.valid_from,
                                                  cert_info.valid_until),
                ' - Issuer: ' + cert_info.issuer_dname,
                ' - Fingerprint: ' + cert_info.fingerprint])

    if may_save:
      buf.append('(R)eject, accept (t)emporarily or accept (p)ermanently? ')
    else:
      buf.append('(R)eject or accept (t)emporarily? ')

    sys.stderr.write('\n'.join(buf))
    sys.stderr.flush()
    choice = sys.stdin.readline().strip().lower()

    if choice == 't':
      result = svn.core.svn_auth_cred_ssl_server_trust_t()
      result.may_save = False
      result.accepted_failures = failures
    elif may_save and choice == 'p':
      result = svn.core.svn_auth_cred_ssl_server_trust_t()
      result.may_save = True
      result.accepted_failures = failures
    else:
      result = None

    return result

  # SSLClientCertPrompt and SSLClientCertPwPrompt are untested.
  def SSLClientCertPrompt(realm, may_save, pool):
    if not interactive:
        return
    result = svn.core.svn_auth_cred_ssl_client_cert_t()
    result.may_save = may_save
    sys.stderr.write('Authentication realm: %s\n' % (realm,))
    sys.stderr.write('Client certificate filename: ')
    sys.stderr.flush()
    result.cert_file = sys.stdin.readline().strip()
    return result

  def SSLClientCertPwPrompt(realm, may_save, pool):
    if not interactive:
        return
    result = svn.core.svn_auth_cred_ssl_client_cert_pw_t()
    result.may_save = may_save
    result.password = _getpass("Passphrase for '%s': " % (realm,),
                               stream=sys.stderr)
    return result

  providers = [
    svn.client.get_simple_provider(),
    svn.client.get_username_provider(),
    ]
  try:
    providers.append(svn.core.svn_auth_get_windows_ssl_server_trust_provider())
  except AttributeError:
    pass
  providers.extend([
    svn.client.get_ssl_server_trust_file_provider(),
    svn.client.get_ssl_client_cert_file_provider(),
    svn.client.get_ssl_client_cert_pw_file_provider(),
    ])

  try:
    # Mac OS X keychain integration
    providers.append(svn.core.svn_auth_get_keychain_simple_provider())
  except AttributeError:
    pass
  try:
    providers.append(svn.core.svn_auth_get_windows_simple_provider())
  except AttributeError:
    pass
  providers.extend([
      svn.client.get_simple_prompt_provider(SimplePrompt, 2),
      svn.client.get_username_prompt_provider(UsernamePrompt, 2),
      svn.client.get_ssl_server_trust_prompt_provider(SSLServerTrustPrompt),
      svn.client.get_ssl_client_cert_prompt_provider(SSLClientCertPrompt, 2),
      svn.client.get_ssl_client_cert_pw_prompt_provider(SSLClientCertPwPrompt,
                                                        2),
      ])

  auth_baton = svn.core.svn_auth_open(providers, pool)

  svn_config_dir_utf8 = config.svn_config_dir.encode('utf8')
  svn.core.svn_auth_set_parameter(auth_baton,
                                  svn.core.SVN_AUTH_PARAM_CONFIG_DIR,
                                  svn_config_dir_utf8)

  svn.core.svn_auth_set_parameter(auth_baton,
                                  svn.core.SVN_AUTH_PARAM_DEFAULT_USERNAME,
                                  username)

  if not interactive:
    svn.core.svn_auth_set_parameter(auth_baton,
                                    svn.core.SVN_AUTH_PARAM_NON_INTERACTIVE,
                                    '')

  if not config.store_passwords:
    svn.core.svn_auth_set_parameter(auth_baton,
                                  svn.core.SVN_AUTH_PARAM_DONT_STORE_PASSWORDS,
                                    '')
  if not config.store_auth_creds:
    svn.core.svn_auth_set_parameter(auth_baton,
                                    svn.core.SVN_AUTH_PARAM_NO_AUTH_CACHE,
                                    '')

  # The bindings don't hold references to my callbacks, so when we
  # return, they're all freed.  Work around it for now.
  return (svn_config_dir_utf8, SimplePrompt, UsernamePrompt, SSLServerTrustPrompt, SSLClientCertPrompt, SSLClientCertPwPrompt, auth_baton)
